//
//  PLEndpoint.m
//  Plankton
//
//  Created by Marc Schlichte on 2008-05-03.
//  Copyright 2008 Framework Labs. All rights reserved.
//

#import "PLEndpoint.h"

#import <netinet/in.h>


@implementation PLEndpoint

@synthesize port, delegate;

- (void)_notifyProblem:(NSString *)problem
{
	if ([delegate respondsToSelector:@selector(endpoint:encounteredProblem:)]) {
		[delegate endpoint:self encounteredProblem:problem];
	}
}

static void HandleSocketData(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info)
{
	PLEndpoint *endpoint = (PLEndpoint *)info;
	id delegate = endpoint.delegate;
	if ([delegate respondsToSelector:@selector(endpoint:didReceiveData:from:)]) {
		[delegate endpoint:endpoint didReceiveData:(NSData *)data from:(NSData *)address];
	}
}

- (id)init
{
	if (self = [super init]) {
		
		// create a dgram socket
		CFSocketContext ctx;
		ctx.version = 0;
		ctx.info = self;
		ctx.retain = NULL;
		ctx.release =  NULL;
		ctx.copyDescription = NULL;
		sock = CFSocketCreate(NULL, PF_INET, SOCK_DGRAM, 0, kCFSocketDataCallBack, HandleSocketData, &ctx);
		if (!sock) {
			[self _notifyProblem:@"can't create socket"];
			[self release];
			return nil;
		}
		
		// assign own address to socket - specifying 0 for arbitrary port
		struct sockaddr_in saddr;
		saddr.sin_family = AF_INET;
		saddr.sin_port = htons(0);
		saddr.sin_addr.s_addr = INADDR_ANY;
		CFDataRef daddr = CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)&saddr, sizeof(saddr), kCFAllocatorNull);
		CFSocketError err = CFSocketSetAddress(sock, daddr);
		CFRelease(daddr);
		if (err) {
			[self _notifyProblem:@"can't set address"];
			[self release];
			return nil;
		}
		
		// extract real port
		daddr = CFSocketCopyAddress(sock);
		struct sockaddr_in *saddrp = (struct sockaddr_in *)CFDataGetBytePtr(daddr);
		port = ntohs(saddrp->sin_port);
		CFRelease(daddr);
		
		// create runloop source and add to current run-loop
		CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(NULL, sock, 0);
		CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
		CFRelease(source);
	}
	return self;
}

- (void)dealloc
{
	if (sock) CFRelease(sock);
	[super dealloc];
}

- (NSString *)hostname
{
	char buf[256];
	if (gethostname(buf, 255)) {
		[self _notifyProblem:@"gethostname failed"];
		return @"locahost";
	}
	return [NSString stringWithFormat:@"%s.", buf];
}

static in_addr_t GetIPForHost(NSString *hostname) 
{
	CFHostRef host = CFHostCreateWithName(NULL, (CFStringRef)hostname);
	if (!CFHostStartInfoResolution(host, kCFHostAddresses, NULL)) {
		CFRelease(host);
		NSLog(@"can't resolve address for host:%@", hostname);
		return 0;
	}
	CFArrayRef addrs = CFHostGetAddressing(host, NULL);
	CFIndex count = CFArrayGetCount(addrs);
	in_addr_t addr = 0;
	for (CFIndex i = 0; i < count; ++i) {
		CFDataRef daddr = (CFDataRef)CFArrayGetValueAtIndex(addrs, i);
		struct sockaddr_in *saddr = (struct sockaddr_in *)CFDataGetBytePtr(daddr);
		addr = saddr->sin_addr.s_addr;
		if (addr) {
			break;
		}
	}
	CFRelease(host);
	
	return addr;
}

- (void)sendData:(NSData *)data toHost:(NSString *)host port:(unsigned short)toPort;
{
	struct sockaddr_in saddr;
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(toPort);
	saddr.sin_addr.s_addr = GetIPForHost(host);
	CFDataRef daddr = CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)&saddr, sizeof(saddr), kCFAllocatorNull);
	
	CFSocketError err = CFSocketSendData(sock, daddr, (CFDataRef)data, 0);
	CFRelease(daddr);
	
	if (err) {
		[self _notifyProblem:@"can't send data"];
	}
}

@end

